package net.worldscale.xp.wslib.wsxml.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Hashtable;
import java.util.Vector;

import net.worldscale.xp.wslib.wsxml.sax.IAttributes;
import net.worldscale.xp.wslib.wsxml.sax.ILocator;
import net.worldscale.xp.wslib.wsxml.sax.SaxException;
import net.worldscale.xp.wslib.wsxml.sax.SaxHandler;
import net.worldscale.xp.wslib.wsxml.sax.SaxParseException;
import net.worldscale.xp.wslib.wsxml.sax.SaxParser;

/**
 *
 * @author Kun<xukun17@sina.com>
 *
 * 实现一个仅支持 XML 的解析器
 *
 */
final class ImplSaxParser extends SaxParser {

    public ImplSaxParser() {
    }

    @Override
    public Object getProperty(String name) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void parse(InputStream is, SaxHandler h) throws IOException, SaxException {
        MyLocator locator = null;
        MyAbstractHandlerProxy hProxy = null;
        try {
            final MyContext pc = new MyContext();
            pc.source = is;
            pc.target = h;

            pc.charReader = new InputStreamReader(is, "utf-8");
            pc.locator = new MyLocator();
            pc.xmlReader = new MyXmlReader(pc);
            pc.handlerProxy = new MyXmlHandlerProxy(pc);

            locator = pc.locator;
            hProxy = pc.handlerProxy;

            MyCore.parse(pc);
        } catch (SaxException e) {
            e.printStackTrace();
            System.out.println("    line=" + locator.getLineNumber() + " , col=" + locator.getColumnNumber() + "");
            if (e instanceof SaxParseException) {
                hProxy.error((SaxParseException) e);
            }
            throw e;
        }
    }

    @Override
    public void setProperty(String name, Object value) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    private static class MyContext {

        InputStream source;
        SaxHandler target;
        InputStreamReader charReader;
        MyXmlReader xmlReader;
        MyAbstractHandlerProxy handlerProxy;
        MyLocator locator;
    }

    private static class MyCore {

        public static void parse(MyContext pc) throws SaxException, IOException {
            pc.handlerProxy.setDocumentLocator(pc.locator);
            pc.handlerProxy.startDocument();
            for (;;) {
                if (pc.xmlReader.isEOF()) {
                    break;
                }
                parseText(pc);
                if (pc.xmlReader.isEOF()) {
                    break;
                }
                parseMarkup(pc);
            }
            pc.handlerProxy.endDocument();
        }

        public static void parseText(MyContext pc) throws SaxException, IOException {
            final String str = pc.xmlReader.readUntil('<', true);
            char[] chs = str.toCharArray();
            // trim
            final MyTrim trim = new MyTrim(chs, 0, chs.length);
            if (trim.getLength() > 0) {
                pc.handlerProxy.characters(trim.getData(), trim.getOffset(), trim.getLength());
            }
        }

        public static void parseMarkup(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck('<');
            final int ch = rdr.getChar();
            if (isNameStartChar(ch)) {
                // element
                parseElementBegin(pc);
            } else {
                // non-element
                // -------------------------------------
                // <!--xxxx--> Comments
                // <![CDATA[xxx]]> CDATA Sections
                // <!DOCTYPE greeting SYSTEM "hello.dtd"> DTD
                // <?PITarget xxxx ?> Processing Instructions
                // <?xml version="1.1"?> XMLDecl
                // <tagName attrName="attrValue" /> Element
                // -------------------------------------
                if (rdr.isStartWith("/")) {
                    parseElementEnd(pc);
                } else if (rdr.isStartWith("!--")) {
                    parseComment(pc);
                } else if (rdr.isStartWith("![CDATA[")) {
                    parseCDATA(pc);
                } else if (rdr.isStartWithIgnoreCase("!DOCTYPE")) {
                    parseDTD(pc);
                } else if (rdr.isStartWith("?xml")) {
                    parseXMLDecl(pc);
                } else if (rdr.isStartWith("?")) {
                    parsePI(pc);
                } else {
                    String prefix = rdr.readChars(10);
                    throw new SaxException("Unsupported markup: <" + prefix + " ... >");
                }
            }
            rdr.readAndCheck('>');
        }

        public static void parseElementBegin(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            final String qName = rdr.readNameString();
            rdr.skipSapce();
            final MyAttributeList attrList = new MyAttributeList();
            for (int ch = rdr.getChar(); ((ch != '>') && (ch != '/')); ch = rdr.getChar()) {
                final String key, value;
                key = rdr.readNameString();
                rdr.skipSapce();
                rdr.readAndCheck('=');
                rdr.skipSapce();
                final int mark = rdr.readChar();
                if ((mark != '\'') && (mark != '"')) {
                    throw new SaxParseException("");
                }
                value = rdr.readUntil(mark, true);
                rdr.readAndCheck(mark);
                attrList.addAttr(key, value);
                rdr.skipSapce();
            }
            pc.handlerProxy.startElement(null, null, qName, attrList);
            final int ch = rdr.getChar();
            if (ch == '/') {
                pc.handlerProxy.endElement(null, null, qName);
                rdr.readChar();
            }
        }

        public static void parseElementEnd(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck('/');
            final String qName = rdr.readNameString();
            rdr.skipSapce();
            pc.handlerProxy.endElement(null, null, qName);
        }

        public static void parseCDATA(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck("![CDATA[");
            final String str = rdr.readUntil("]]>", false);
            rdr.readAndCheck("]]");
            final char[] chs = str.toCharArray();
            pc.handlerProxy.characters(chs, 0, chs.length);
        }

        public static void parseComment(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readAndCheck("!--");
            pc.xmlReader.readUntil("-->", false);
            rdr.readAndCheck("--");
        }

        public static void parseDTD(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readUntil('>', false);
        }

        public static void parsePI(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readUntil('>', false);
        }

        public static void parseXMLDecl(MyContext pc) throws SaxException, IOException {
            final MyXmlReader rdr = pc.xmlReader;
            rdr.readUntil('>', false);
        }
    }

    private static boolean isNameChar(int ch) {
        if (isNameStartChar(ch)) {
            return true;
        } else if (('0' <= ch) && (ch <= '9')) {
            return true;
        } else {
            switch (ch) {
                case '.':
                case '-':
                    return true;
                default:
                    return false;
            }
        }
    }

    private static boolean isNameStartChar(int ch) {
        if (('a' <= ch) && (ch <= 'z')) {
            return true;
        } else if (('A' <= ch) && (ch <= 'Z')) {
            return true;
        } else {
            switch (ch) {
                case ':':
                case '_':
                    return true;
                default:
                    return false;
            }
        }
    }

    private static abstract class MyAbstractHandlerProxy extends SaxHandler {
    }

    private static class MyLocator implements ILocator {

        //     private int mColNum;
        //     private int mLineNum;
        private String mPubId;
        private String mSysId;
        private int mCnt0a = 0;
        private int mCnt0d = 0;
        private int mOffsetInLine = 0;

        public MyLocator() {
        }

        public int getColumnNumber() {
            return this.mOffsetInLine;
        }

        public int getLineNumber() {
            return ((this.mCnt0a < this.mCnt0d) ? this.mCnt0d : this.mCnt0a);
        }

        public String getPublicId() {
            return mPubId;
        }

        public String getSystemId() {
            return mSysId;
        }

        public void write(int ch) {
            switch (ch) {
                case 0x0a:
                    this.mOffsetInLine = 0;
                    this.mCnt0a++;
                    break;
                case 0x0d:
                    this.mOffsetInLine = 0;
                    this.mCnt0d++;
                    break;
                default:
                    this.mOffsetInLine++;
            }
        }
    }

    private static class MyAttributeList implements IAttributes {

        private final Vector<String> mNames = new Vector<String>(2);
        private final Vector<String> mValues = new Vector<String>(2);

        public MyAttributeList() {
        }

        public void addAttr(String qName, String value) {
            if ((qName == null) || (value == null)) {
                throw new RuntimeException(
                        "The name or value of attribute is null.");
            }
            this.mNames.addElement(qName);
            this.mValues.addElement(value);
        }

        @Override
        public int getIndex(String qName) {
            return this.mNames.indexOf(qName);
        }

        @Override
        public int getIndex(String uri, String localName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public int getLength() {
            return this.mNames.size();
        }

        @Override
        public String getLocalName(int index) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getQName(int index) {
            return this.mNames.elementAt(index);
        }

        @Override
        public String getType(int index) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getType(String uri, String localName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getType(String qName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getURI(int index) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getValue(String uri, String localName) {
            throw new RuntimeException("Unsupported operation.");
        }

        @Override
        public String getValue(int index) {
            return this.mValues.elementAt(index);
        }

        @Override
        public String getValue(String qName) {
            final int index = this.mNames.indexOf(qName);
            return this.mValues.elementAt(index);
        }
    }

    private static class MyXmlReaderBuffer {

        /**
         * this is a FIFO buffer.
         */
        private static class _Core {

            private final int[] data;
            private int wptr = 0;
            private int rptr = 0;

            public _Core(int size) {
                if (size < 10) {
                    size = 10;
                }
                data = new int[size];
            }

            public _Core(_Core src, int size) throws SaxParseException {
                if (size < src.data.length) {
                    size = src.data.length;
                }
                data = new int[size];
                while (src.getLength() > 0) {
                    this.write(src.read());
                }
            }

            public final void write(int ch) throws SaxParseException {
                if ((wptr - rptr) < data.length) {
                    data[(wptr++) % data.length] = ch;
                } else {
                    throw new SaxParseException("MyXmlReaderBuffer write overflow.");
                }
            }

            public final int read() throws SaxParseException {
                if (rptr < wptr) {
                    return data[(rptr++) % data.length];
                } else {
                    throw new SaxParseException("MyXmlReaderBuffer read overflow.");
                }
            }

            public int getCap() {
                return data.length;
            }

            public final int getLength() {
                return (wptr - rptr);
            }

            public boolean isFull() {
                return ((wptr - rptr) >= data.length);
            }

            public int charAt(int index) throws SaxParseException {
                if ((0 <= index) && (index < (wptr - rptr))) {
                    return data[(rptr + index) % data.length];
                } else {
                    throw new SaxParseException("MyXmlReaderBuffer get overflow.");
                }
            }
        }
        private _Core core = new _Core(16);

        public MyXmlReaderBuffer() {
        }

        public int size() {
            return core.getLength();
        }

        public String getContentString() throws SaxException {
            final int len = this.core.getLength();
            final StringBuffer sb = new StringBuffer();
            for (int i = 0; i < len; i++) {
                sb.append((char) this.core.charAt(i));
            }
            return sb.toString();
        }

        public int read() throws SaxParseException {
            return core.read();
        }

        public void write(int ch) throws SaxParseException {
            core.write(ch);
        }

        public int charAt(int index) throws SaxParseException {
            return core.charAt(index);
        }
    }

    private abstract static class MyAbstractStringBuffer {

        public abstract void append(int ch) throws SaxException;

        public abstract String getString();
    }

    private static class MyEscapeCharMapper {

        private static MyEscapeCharMapper sInst;

        public static MyEscapeCharMapper getInstance() {
            if (sInst == null) {
                sInst = new MyEscapeCharMapper();
            }
            return sInst;
        }
        private final Hashtable<String, Character> mS2CTable;

        private MyEscapeCharMapper() {
            final Hashtable<String, Character> s2c = new Hashtable<String, Character>();
            mS2CTable = s2c;
            s2c.put("&gt;", '>');
            s2c.put("&lt;", '<');
            s2c.put("&amp;", '&');
            s2c.put("&apos;", '\'');
            s2c.put("&quot;", '"');
            s2c.put("&nbsp;", ' ');
        }

        public char fromStringToChar(String str) throws SaxException {
            final Character ret = this.mS2CTable.get(str);
            if (ret != null) {
                return ret;
            }
            if ((str.startsWith("&#x")) && (str.endsWith(";"))) {
                final int len = str.length();
                String s = str.substring(3, len - 1);
                return (char) Integer.parseInt(s, 16);
            }
            throw new SaxParseException("Unsupported escape char: " + str);
        }
    }

    private static char unescapeStringToChar(String str) throws SaxException {
        return MyEscapeCharMapper.getInstance().fromStringToChar(str);
    }

    private static class MyUnescapeStringBuffer extends MyAbstractStringBuffer {

        private final StringBuffer mMainSB;
        private final StringBuffer mEscSB;
        private boolean mIsInEscape;

        public MyUnescapeStringBuffer() {
            this.mMainSB = new StringBuffer();
            this.mEscSB = new StringBuffer();
            mIsInEscape = false;
        }

        public void append(int ch) throws SaxException {
            if (mIsInEscape) {
                // in escape
                this.mEscSB.append((char) ch);
                if (ch == ';') {
                    mIsInEscape = false;
                    char esc = unescapeStringToChar(mEscSB.toString());
                    mMainSB.append(esc);
                }
            } else {
                // out of escape
                if (ch == '&') {
                    mEscSB.setLength(0);
                    mEscSB.append((char) ch);
                    mIsInEscape = true;
                } else {
                    mMainSB.append((char) ch);
                }
            }
        }

        @Override
        public String getString() {
            return this.mMainSB.toString();
        }
    }

    private static class MyPlainStringBuffer extends MyAbstractStringBuffer {

        private final StringBuffer mMainSB;

        public MyPlainStringBuffer() {
            this.mMainSB = new StringBuffer();
        }

        public void append(int ch) {
            this.mMainSB.append((char) ch);
        }

        @Override
        public String getString() {
            return this.mMainSB.toString();
        }
    }

    private static class MyXmlReader {

        private final MyContext mpc;
        private final MyXmlReaderBuffer mBuffer;

        public MyXmlReader(MyContext pc) throws IOException, SaxParseException {
            mBuffer = new MyXmlReaderBuffer();
            mpc = pc;
        }

        private void readCharactersToBuffer(int minBufferLength) throws IOException, SaxException {
            while (this.mBuffer.size() < minBufferLength) {
                this.mBuffer.write(this.mpc.charReader.read());
            }
        }

        public boolean isEOF() throws SaxException, IOException {
            return (this.getChar() < 0);
        }

        public String readUntil(int ch, boolean unescape) throws SaxException, IOException {
            final MyAbstractStringBuffer sbuf;
            if (unescape) {
                sbuf = new MyUnescapeStringBuffer();
            } else {
                sbuf = new MyPlainStringBuffer();
            }
            for (;;) {
                final int ch2 = this.getChar();
                if ((ch2 < 0) || (ch2 == ch)) {
                    break;
                }
                sbuf.append(this.readChar());
            }
            return sbuf.getString();
        }

        public String readUntil(String str, boolean unescape) throws SaxException, IOException {
            final MyAbstractStringBuffer sbuf;
            if (unescape) {
                sbuf = new MyUnescapeStringBuffer();
            } else {
                sbuf = new MyPlainStringBuffer();
            }
            final int ch = str.charAt(0);
            for (;;) {
                final int ch2 = this.getChar();
                if (ch2 < 0) {
                    break;
                }
                if (ch2 == ch) {
                    if (this.isStartWith(str)) {
                        break;
                    }
                }
                sbuf.append(this.readChar());
            }
            return sbuf.getString();
        }

        public void readAndCheck(int ch) throws SaxException, IOException {
            final int ch2 = this.readChar();
            if (ch2 != ch) {
                throw new SaxParseException("Here need a char of '" + ((char) ch) + "' but a '" + ((char) ch2) + "'.");
            }
        }

        public void readAndCheck(String str) throws SaxException, IOException {
            final int len = str.length();
            for (int i = 0; i < len; i++) {
                readAndCheck(str.charAt(i));
            }
        }

        public int getChar() throws SaxException, IOException {
            readCharactersToBuffer(1);
            return this.mBuffer.charAt(0);
        }

        public String readChars(int maxLength) throws IOException, SaxException {
            final StringBuffer sb = new StringBuffer();
            for (int n = maxLength; n > 0; n--) {
                int ch = this.readChar();
                if (ch < 0) {
                    break;
                } else {
                    sb.append((char) ch);
                }
            }
            return sb.toString();
        }

        public int readChar() throws SaxException, IOException {
            readCharactersToBuffer(1);
            int ch = this.mBuffer.read();
            MyLocator loc = this.mpc.locator;
            loc.write(ch);
            return ch;
        }

        public String readNameString() throws SaxException, IOException {
            int ch = this.readChar();
            if (!isNameStartChar(ch)) {
                throw new SaxParseException("Here need a Name-Start-Char but a '" + ((char) ch) + "'.");
            }
            final StringBuffer sb = new StringBuffer();
            sb.append((char) ch);
            while (isNameChar(this.getChar())) {
                sb.append((char) this.readChar());
            }
            return sb.toString();
        }

        public boolean isStartWith(String str) throws SaxException, IOException {
            readCharactersToBuffer(str.length());
            final String str2 = this.mBuffer.getContentString();
            return str2.startsWith(str);
        }

        public boolean isStartWithIgnoreCase(String str) throws SaxException, IOException {
            readCharactersToBuffer(str.length());
            final String str2 = this.mBuffer.getContentString().toLowerCase();
            return str2.startsWith(str.toLowerCase());
        }

        public void skipSapce() throws SaxException, IOException {
            for (;;) {
                final int ch = this.getChar();
                switch (ch) {
                    case ' ':
                    case 0x09:
                    case 0x0a:
                    case 0x0d:
                        break;
                    default:
                        return;
                }
                this.readChar();
            }
        }
    }

    private static class MyXmlHandlerProxy extends MyAbstractHandlerProxy {

        private final MyContext mpc;
        private final SaxHandler mTarget;

        public MyXmlHandlerProxy(MyContext pc) {
            mpc = pc;
            this.mTarget = mpc.target;
        }

        @Override
        public void warning(SaxParseException e) throws SaxException {
            mTarget.warning(e);
        }

        @Override
        public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SaxException {
            mTarget.unparsedEntityDecl(name, publicId, systemId, notationName);
        }

        @Override
        public void startPrefixMapping(String prefix, String uri) throws SaxException {
            mTarget.startPrefixMapping(prefix, uri);
        }

        @Override
        public void startElement(String uri, String localName, String qName, IAttributes attributes) throws SaxException {
            mTarget.startElement(uri, localName, qName, attributes);
        }

        @Override
        public void startDocument() throws SaxException {
            mTarget.startDocument();
        }

        @Override
        public void skippedEntity(String name) throws SaxException {
            mTarget.skippedEntity(name);
        }

        @Override
        public void setDocumentLocator(ILocator locator) throws SaxException {
            mTarget.setDocumentLocator(locator);
        }

        @Override
        public void processingInstruction(String target, String data) throws SaxException {
            mTarget.processingInstruction(target, data);
        }

        @Override
        public void notationDecl(String name, String publicId, String systemId) throws SaxException {
            mTarget.notationDecl(name, publicId, systemId);
        }

        @Override
        public void ignorableWhitespace(char[] ch, int start, int length) throws SaxException {
            mTarget.ignorableWhitespace(ch, start, length);
        }

        @Override
        public void fatalError(SaxParseException e) throws SaxException {
            mTarget.fatalError(e);
        }

        @Override
        public void error(SaxParseException e) throws SaxException {
            mTarget.error(e);
        }

        @Override
        public void endPrefixMapping(String prefix) throws SaxException {
            mTarget.endPrefixMapping(prefix);
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SaxException {
            mTarget.endElement(uri, localName, qName);
        }

        @Override
        public void endDocument() throws SaxException {
            mTarget.endDocument();
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SaxException {
            mTarget.characters(ch, start, length);
        }
    }

    private static class MyTrim {

        final int mOffset;
        final int mLength;
        final char[] mData;

        public MyTrim(final char[] chs, final int offset, final int length) {
            this.mData = chs;
            final int end = offset + length;
            int i = offset;
            for (; i < end; i++) {
                if (!isSpaceChar(chs[i])) {
                    break;
                }
            }
            this.mOffset = i;
            for (i = end - 1; mOffset <= i; i--) {
                if (!isSpaceChar(chs[i])) {
                    break;
                }
            }
            this.mLength = (i - mOffset) + 1;
        }

        private static boolean isSpaceChar(char ch) {
            switch (ch) {
                case ' ':
                case 0x09:
                case 0x0a:
                case 0x0d:
                    return true;
                default:
                    return false;
            }
        }

        public char[] getData() {
            return mData;
        }

        public int getOffset() {
            return mOffset;
        }

        public int getLength() {
            return mLength;
        }
    }
}
